using System;
using System.IO;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell;
using EnvDTE;
using EnvDTE80;
using Microsoft.Win32;
using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VSSDK.Tools.VsIdeTesting;
using Microsoft.VisualStudio;

namespace Microsoft.VsSDK.IntegrationTestLibrary
{
	/// <summary>
	/// </summary>
	public class TestUtils
	{

		#region Methods: Handling embedded resources
		/// <summary>
		/// Gets the embedded file identified by the resource name, and converts the
		/// file into a string.
		/// </summary>
		/// <param name="resourceName">In VS, is DefaultNamespace.FileName?</param>
		/// <returns></returns>
		public static string GetEmbeddedStringResource(Assembly assembly, string resourceName)
		{
			string result = null;

			// Use the .NET procedure for loading a file embedded in the assembly
			Stream stream = assembly.GetManifestResourceStream(resourceName);
			if (stream != null)
			{
				// Convert bytes to string
				byte[] fileContentsAsBytes = new byte[stream.Length];
				stream.Read(fileContentsAsBytes, 0, (int)stream.Length);
				result = Encoding.Default.GetString(fileContentsAsBytes);
			}
			else
			{
				// Embedded resource not found - list available resources
				Debug.WriteLine("Unable to find the embedded resource file '" + resourceName + "'.");
				Debug.WriteLine("  Available resources:");
				foreach (string aResourceName in assembly.GetManifestResourceNames())
				{
					Debug.WriteLine("    " + aResourceName);
				}
			}

			return result;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="embeddedResourceName"></param>
		/// <param name="baseFileName"></param>
		/// <param name="fileExtension"></param>
		/// <returns></returns>
		public static void WriteEmbeddedResourceToFile(Assembly assembly, string embeddedResourceName, string fileName)
		{
			// Get file contents
			string fileContents = GetEmbeddedStringResource(assembly, embeddedResourceName);
			if (fileContents == null)
				throw new ApplicationException("Failed to get embedded resource '" + embeddedResourceName + "' from assembly '" + assembly.FullName);

			// Write to file
			StreamWriter sw = new StreamWriter(fileName);
			sw.Write(fileContents);
			sw.Close();
		}

		/// <summary>
		/// Writes an embedded resource to a file.
		/// </summary>
		/// <param name="assembly">The name of the assembly that the embedded resource is defined.</param>
		/// <param name="embeddedResourceName">The name of the embedded resource.</param>
		/// <param name="fileName">The file to write the embedded resource's content.</param>
		public static void WriteEmbeddedResourceToBinaryFile(Assembly assembly, string embeddedResourceName, string fileName)
		{
			// Get file contents
			Stream stream = assembly.GetManifestResourceStream(embeddedResourceName);
			if (stream == null)
				throw new InvalidOperationException("Failed to get embedded resource '" + embeddedResourceName + "' from assembly '" + assembly.FullName);

			// Write to file
			BinaryWriter sw = null;
			FileStream fs = null;
			try
			{
				byte[] fileContentsAsBytes = new byte[stream.Length];
				stream.Read(fileContentsAsBytes, 0, (int)stream.Length);

				FileMode mode = FileMode.CreateNew;
				if (File.Exists(fileName))
				{
					mode = FileMode.Truncate;
				}

				fs = new FileStream(fileName, mode);

				sw = new BinaryWriter(fs);
				sw.Write(fileContentsAsBytes);
			}
			finally
			{
				if (fs != null)
				{
					fs.Close();
				}
				if (sw != null)
				{
					sw.Close();
				}
			}
		}

		#endregion

		#region Methods: Handling temporary files and directories
		/// <summary>
		/// Returns the first available file name on the form
		///   [baseFileName]i.[extension]
		/// where [i] starts at 1 and increases until there is an available file name
		/// in the given directory. Also creates an empty file with that name to mark
		/// that file as occupied.
		/// </summary>
		/// <param name="directory">Directory that the file should live in.</param>
		/// <param name="baseFileName"></param>
		/// <param name="extension">may be null, in which case the .[extension] part
		/// is not added.</param>
		/// <returns>Full file name.</returns>
		public static string GetNewFileName(string directory, string baseFileName, string extension)
		{
			// Get the new file name
			string fileName = GetNewFileOrDirectoryNameWithoutCreatingAnything(directory, baseFileName, extension);

			// Create an empty file to mark it as taken
			StreamWriter sw = new StreamWriter(fileName);

			sw.Write("");
			sw.Close();
			return fileName;
		}
		/// <summary>
		/// Returns the first available directory name on the form
		///   [baseDirectoryName]i
		/// where [i] starts at 1 and increases until there is an available directory name
		/// in the given directory. Also creates the directory to mark it as occupied.
		/// </summary>
		/// <param name="directory">Directory that the file should live in.</param>
		/// <param name="baseDirectoryName"></param>
		/// <returns>Full directory name.</returns>
		public static string GetNewDirectoryName(string directory, string baseDirectoryName)
		{
			// Get the new file name
			string directoryName = GetNewFileOrDirectoryNameWithoutCreatingAnything(directory, baseDirectoryName, null);

			// Create an empty directory to make it as occupied
			Directory.CreateDirectory(directoryName);

			return directoryName;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="directory"></param>
		/// <param name="baseFileName"></param>
		/// <param name="extension"></param>
		/// <returns></returns>
		private static string GetNewFileOrDirectoryNameWithoutCreatingAnything(string directory, string baseFileName, string extension)
		{
			// - get a file name that we can use
			string fileName;
			int i = 1;

			string fullFileName = null;
			while (true)
			{
				// construct next file name
				fileName = baseFileName + i;
				if (extension != null)
					fileName += '.' + extension;

				// check if that file exists in the directory
				fullFileName = Path.Combine(directory, fileName);

				if (!File.Exists(fullFileName) && !Directory.Exists(fullFileName))
					break;
				else
					i++;
			}

			return fullFileName;
		}
		#endregion

		#region Methods: Handling solutions
		/// <summary>
		/// Closes the currently open solution (if any), and creates a new solution with the given name.
		/// </summary>
		/// <param name="solutionName">Name of new solution.</param>
		public void CreateEmptySolution(string directory, string solutionName)
		{
			CloseCurrentSolution(__VSSLNSAVEOPTIONS.SLNSAVEOPT_NoSave);

			string solutionDirectory = GetNewDirectoryName(directory, solutionName);

			// Create and force save solution
			IVsSolution solutionService = (IVsSolution)VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsSolution));
			solutionService.CreateSolution(solutionDirectory, solutionName, (uint)__VSCREATESOLUTIONFLAGS.CSF_SILENT);
			solutionService.SaveSolutionElement((uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, null, 0);
			DTE dte = VsIdeTestHostContext.Dte;
			Assert.AreEqual(solutionName + ".sln", Path.GetFileName(dte.Solution.FileName), "Newly created solution has wrong Filename");
		}

		public void CloseCurrentSolution(__VSSLNSAVEOPTIONS saveoptions)
		{
			// Get solution service
			IVsSolution solutionService = (IVsSolution)VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsSolution));

			// Close already open solution
			solutionService.CloseSolutionElement((uint)saveoptions, null, 0);
		}

		public void ForceSaveSolution()
		{
			// Get solution service
			IVsSolution solutionService = (IVsSolution)VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsSolution));

			// Force-save the solution
			solutionService.SaveSolutionElement((uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, null, 0);
		}

		/// <summary>
		/// Get current number of open project in solution
		/// </summary>
		/// <returns></returns>
		public int ProjectCount()
		{
			// Get solution service
			IVsSolution solutionService = (IVsSolution)VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsSolution));
			object projectCount;
			solutionService.GetProperty((int)__VSPROPID.VSPROPID_ProjectCount, out projectCount);
			return (int)projectCount;
		}
		#endregion

		#region Methods: Handling projects
		/// <summary>
		/// Creates a project.
		/// </summary>
		/// <param name="projectName">Name of new project.</param>
		/// <param name="templateName">Name of project template to use</param>
		/// <param name="language">language</param>
		/// <returns>New project.</returns>
		public void CreateProjectFromTemplate(string projectName, string templateName, string language, bool exclusive)
		{
			DTE dte = (DTE)VsIdeTestHostContext.ServiceProvider.GetService(typeof(DTE));

			Solution2 sol = dte.Solution as Solution2;
			string projectTemplate = sol.GetProjectTemplate(templateName, language);

			// - project name and directory
			string solutionDirectory = Directory.GetParent(dte.Solution.FullName).FullName;
			string projectDirectory = GetNewDirectoryName(solutionDirectory, projectName);

			dte.Solution.AddFromTemplate(projectTemplate, projectDirectory, projectName, false);
		}
		#endregion

		#region Methods: Handling project items
		/// <summary>
		/// Create a new item in the project
		/// </summary>
		/// <param name="parent">the parent collection for the new item</param>
		/// <param name="templateName"></param>
		/// <param name="language"></param>
		/// <param name="name"></param>
		/// <returns></returns>
		public ProjectItem AddNewItemFromVsTemplate(ProjectItems parent, string templateName, string language, string name)
		{
			if (parent == null)
				throw new ArgumentException("project");
			if (name == null)
				throw new ArgumentException("name");

			DTE dte = (DTE)VsIdeTestHostContext.ServiceProvider.GetService(typeof(DTE));

			Solution2 sol = dte.Solution as Solution2;

			string filename = sol.GetProjectItemTemplate(templateName, language);

			parent.AddFromTemplate(filename, name);

			return parent.Item(name);
		}

		/// <summary>
		/// Save an open document.
		/// </summary>
		/// <param name="documentMoniker">for filebased documents this is the full path to the document</param>
		public void SaveDocument(string documentMoniker)
		{
			// Get document cookie and hierarchy for the file
			IVsRunningDocumentTable runningDocumentTableService = (IVsRunningDocumentTable)VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsRunningDocumentTable));
			uint docCookie;
			IntPtr docData;
			IVsHierarchy hierarchy;
			uint itemId;
			runningDocumentTableService.FindAndLockDocument(
				(uint)Microsoft.VisualStudio.Shell.Interop._VSRDTFLAGS.RDT_NoLock,
				documentMoniker,
				out hierarchy,
				out itemId,
				out docData,
				out docCookie);

			// Save the document
			IVsSolution solutionService = (IVsSolution)VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsSolution));
			solutionService.SaveSolutionElement((uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_ForceSave, hierarchy, docCookie);
		}

		public void CloseInEditorWithoutSaving(string fullFileName)
		{
			// Get the RDT service
			IVsRunningDocumentTable runningDocumentTableService = (IVsRunningDocumentTable)VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsRunningDocumentTable));
			Assert.IsNotNull(runningDocumentTableService, "Failed to get the Running Document Table Service");

			// Get our document cookie and hierarchy for the file
			uint docCookie;
			IntPtr docData;
			IVsHierarchy hierarchy;
			uint itemId;
			runningDocumentTableService.FindAndLockDocument(
				(uint)Microsoft.VisualStudio.Shell.Interop._VSRDTFLAGS.RDT_NoLock,
				fullFileName,
				out hierarchy,
				out itemId,
				out docData,
				out docCookie);

			// Get the SolutionService
			IVsSolution solutionService = VsIdeTestHostContext.ServiceProvider.GetService(typeof(IVsSolution)) as IVsSolution;
			Assert.IsNotNull(solutionService, "Failed to get IVsSolution service");

			// Close the document
			solutionService.CloseSolutionElement(
				(uint)__VSSLNSAVEOPTIONS.SLNSAVEOPT_NoSave,
				hierarchy,
				docCookie);
		}
		#endregion

		#region Methods: Handling Toolwindows
		public bool CanFindToolwindow(Guid persistenceGuid)
		{
			IVsUIShell uiShellService = VsIdeTestHostContext.ServiceProvider.GetService(typeof(SVsUIShell)) as IVsUIShell;
			Assert.IsNotNull(uiShellService);
			IVsWindowFrame windowFrame;
			int hr = uiShellService.FindToolWindow((uint)__VSFINDTOOLWIN.FTW_fFindFirst, ref persistenceGuid, out windowFrame);
			Assert.IsTrue(hr == VSConstants.S_OK);

			return (windowFrame != null);
		}
		#endregion

		#region Methods: Loading packages
		public IVsPackage LoadPackage(Guid packageGuid)
		{
			IVsShell shellService = (IVsShell)VsIdeTestHostContext.ServiceProvider.GetService(typeof(SVsShell));
			IVsPackage package;
			shellService.LoadPackage(ref packageGuid, out package);
			Assert.IsNotNull(package, "Failed to load package");
			return package;
		}
		#endregion

		/// <summary>
		/// Executes a Command (menu item) in the given context
		/// </summary>
		public void ExecuteCommand(CommandID cmd)
		{
			object Customin = null;
			object Customout = null;
			string guidString = cmd.Guid.ToString("B").ToUpper();
			int cmdId = cmd.ID;
			DTE dte = VsIdeTestHostContext.Dte;
			dte.Commands.Raise(guidString, cmdId, ref Customin, ref Customout);
		}

	}
}
